﻿// opengl_glut_glew_imgui.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "GL/glew.h"
#include "GL/freeglut.h"


//imgui
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_glut.h"
#include "imgui_impl_opengl3.h"

#include "wdirtools.h"
#include "wstringutils.h"
#include "Shader.h"

#include "SimpleHeightMapTerrain.h"
#include "RenderTextureUtil.h"
#include "SimpleSky.h"
#include "SimpleWaterSurface.h"

//globals
glm::vec3 g_eyepos(10.0,105.0, 50.0);
glm::vec3 g_eyetarget(0.0, 0.0, 0.0);
glm::vec3 g_earthPointToSunDir = glm::normalize(glm::vec3(-1, 1, -1));
float g_near = 0.1;
float g_far = 1000;

//imgui
float g_ui_eyeElev = -1.0;// in radian
float g_ui_eyeAzim = 3.14*3/2 ;

void ResizeFunction(int, int);
void RenderFunction(void);
void InitWindow(int xpos, int ypos, int width, int height);

int g_windowWidth = 800;
int g_windowHeight = 600;
Shader* g_shader = nullptr;
Shader* g_skyShader = nullptr;
GLuint g_vao = 0;//this is vertex buffer array object
GLuint g_buffer_vertex = 0;
GLuint g_skyVAO = 0;

//height map
std::shared_ptr<SimpleHeightMapTerrain> g_simpleTerrain;
std::shared_ptr<RenderTextureUtil> g_render2TextureRefractive ;
std::shared_ptr<RenderTextureUtil> g_render2TextureReflective ;
std::shared_ptr<SimpleSky> g_simpleSky;
std::shared_ptr<SimpleWaterSurface> g_simpleWater;


int main() /////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
    InitWindow(100 ,100 , g_windowWidth, g_windowHeight);
    //这一行只能在initwindow下面。
    std::cout << "version v1 sky and terrain done." << std::endl;
    std::cout << "version v2 render to texture done " << std::endl;
    std::cout << "version v3 add water surface working " << std::endl;
    std::cout << "v4 clip plane ok." << endl;
    std::cout << "v5 map reflection texture to water surface good." << endl;
    std::cout << "v6 map refraction texture to water surface good." << endl;
    std::cout << "v7 combine dudv ok." << endl;
    std::cout << "v8 sunglint by normal maps ok." << endl;
    std::cout << "v9 fresnel." << endl;
    // v9 算是最后效果了，使用了菲涅尔反射，
    // 但是效果不是特别好，其实一般大尺度观察水体强吸收也不会出现过多的折射水底效果。
    // 所以实际使用中菲涅尔反射也是可用可不用的。
    const float heightScale = 20.0;
    const float heightOffset = -0.1;//height map will be 0.0 to 1.0
    g_simpleTerrain = std::make_shared<SimpleHeightMapTerrain>("heightmap-oneband", 256, 256, 256, 100 , 20.0, -0.1, "terrain-vert.js", "terrain-frag.js");
    // y = (value + offset) * scale , y: -2.0 to 18.0
    const float waterAltitude = 0.0;
    
    g_render2TextureRefractive = std::make_shared<RenderTextureUtil>();
    g_render2TextureRefractive->_debugName = "refract";
    g_render2TextureReflective = std::make_shared<RenderTextureUtil>();
    g_render2TextureReflective->_debugName = "reflect";
    g_simpleSky = std::make_shared<SimpleSky>();
    g_simpleWater = std::make_shared<SimpleWaterSurface>(waterAltitude, -50.0, 50.0, -50.0, 50.0,
        1.0   , 
        1.0 , 
        heightOffset, heightScale);



    glutMainLoop();

    //imgui shutdown
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGLUT_Shutdown();
    ImGui::DestroyContext();

    exit(EXIT_SUCCESS);
}
                                                                                                                       //
                                                                                                                       //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


void InitWindow(int xpos, int ypos, int width, int height)
{
    int   no_use_argc = 1;
    char* no_use_argv[1];

    glutInit(&no_use_argc, no_use_argv);
    glutInitContextVersion(4, 0);
    glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);
    glutInitContextProfile(GLUT_CORE_PROFILE);
    glutSetOption(
        GLUT_ACTION_ON_WINDOW_CLOSE,
        GLUT_ACTION_GLUTMAINLOOP_RETURNS
    );
    glutInitWindowSize(width, height);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    int WindowHandle = glutCreateWindow("GLUT");
    if (WindowHandle < 1) {
        fprintf(
            stderr,
            "ERROR: Could not create a new rendering window.\n"
        );
        exit(EXIT_FAILURE);
    }

    glutReshapeFunc(ResizeFunction);
    glutDisplayFunc(RenderFunction);

    glewInit();//!!!important

    //imgui initialize
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    ImGui_ImplGLUT_Init();
    ImGui_ImplOpenGL3_Init();
    ImGui_ImplGLUT_InstallFuncs();
}


void ResizeFunction(int Width, int Height)
{
    glViewport(0, 0, Width, Height);
}

void printV4(glm::vec4& v4) {
    cout << "vec4 " << v4.x << "," << v4.y << "," << v4.z << ","<<v4.w << endl;
}

void RenderFunction(void)
{
    glm::mat4 matIden(1.f);

    //imgui gui setup.
    {
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGLUT_NewFrame();
        ImGui::NewFrame();
        ImGuiIO& io = ImGui::GetIO();
        bool bval = false;
        ImGui::Checkbox("Use bool", &bval);
        float somefval = 50;
        ImGui::SliderFloat("eye elev", &g_ui_eyeElev, -3.0 / 2.f, 3.0 / 2.f, "%0.2f");
        ImGui::SliderFloat("eye azim", &g_ui_eyeAzim, 0.0f , 2*3.14159f , "%0.2f");
        glm::vec3 eyepos = g_eyepos;
        ImGui::SliderFloat3("eye pos", glm::value_ptr(eyepos), -200, 200, "%0.2f", 0);
        g_eyepos = eyepos;
        bool buttonClick = ImGui::Button("snapshot");
        if (buttonClick) {
            std::cout << "click" << std::endl;
        }
        ImGui::Render();
    }

    //update
    float xz = cosf(g_ui_eyeElev);
    glm::vec3 eyeDir;
    eyeDir.x = cosf(g_ui_eyeAzim) * xz;
    eyeDir.y = sinf(g_ui_eyeElev);
    eyeDir.z = sinf(g_ui_eyeAzim) * xz;
    g_eyetarget = g_eyepos + eyeDir;

    //reflective
    glm::vec3 eye2 = g_eyepos;
    glm::vec3 tar2 = g_eyetarget;
    eye2.y = -eye2.y;
    tar2.y = -tar2.y;
    glm::mat4 underGroundView = glm::lookAt(eye2, tar2, glm::vec3(0.0, 1.0, 0.0));


    glm::mat4 matView = glm::lookAt(g_eyepos, g_eyetarget, glm::vec3(0.0, 1.0, 0.0));
    glm::mat4 matProj = glm::perspective(glm::pi<float>() * 0.25f, g_windowWidth * 1.f / g_windowHeight, g_near, g_far);
    glm::mat4 invertProjView = glm::inverse(matProj * matView);

    g_simpleTerrain->update(0.0, matProj, matView , g_earthPointToSunDir);
    g_simpleSky->update(matProj, matView, matIden, g_near, g_eyepos, g_earthPointToSunDir, invertProjView);
    g_simpleWater->update(
        matProj 
        , matView
        , matIden 
        , g_earthPointToSunDir,g_eyepos
        , g_render2TextureReflective->getOutputColorTexture()
        , g_render2TextureRefractive->getOutputColorTexture()
        , matProj
        , underGroundView
    );

    //////////////////////////// DRAW to textures ---------------------------
    {
        glm::vec4 waterPlane_p = g_simpleWater->getWaterPlane();
        

        glm::mat4 invertProjView2 = glm::inverse(matProj * underGroundView);
        g_simpleTerrain->setClipPlane(waterPlane_p);
        g_simpleTerrain->setEnableClipPlane(true);
        vector<RenderInterface*> renderArray;
        renderArray.push_back(g_simpleSky.get());
        renderArray.push_back(g_simpleTerrain.get());
        g_simpleTerrain->update(0.0, matProj, underGroundView, g_earthPointToSunDir);
        g_simpleSky->update(matProj, underGroundView, matIden, g_near, eye2, g_earthPointToSunDir, invertProjView2);
        g_render2TextureReflective->render2texture(renderArray);
    }
    {
        glm::vec4 waterPlane_n = g_simpleWater->getWaterPlane(); 
        waterPlane_n.y *= -1; waterPlane_n.w *= -1;
        //refractive
        g_simpleTerrain->setClipPlane(waterPlane_n);
        g_simpleTerrain->setEnableClipPlane(true);
        vector<RenderInterface*> renderArray;
        renderArray.push_back(g_simpleSky.get());
        renderArray.push_back(g_simpleTerrain.get());
        g_simpleTerrain->update(0.0, matProj, matView, g_earthPointToSunDir);
        g_simpleSky->update(matProj, matView, matIden, g_near, g_eyepos, g_earthPointToSunDir, invertProjView);
        g_render2TextureRefractive->render2texture(renderArray);
    }
    



    //////////////////////////// DRAW to screens ----------------------------
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default
    glViewport(0, 0, g_windowWidth, g_windowHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    
    //sky ---
    g_simpleSky->render();
    //sky end ---    
    //glm::vec4 tempWaterPlane(0.0, 1.0, 0.0,1.0);  //用于测试 g_ClipDistance 是否生效。测试通过已经。
    //g_simpleTerrain->setClipPlane(tempWaterPlane);//用于测试 g_ClipDistance 是否生效。测试通过已经。
    //g_simpleTerrain->setEnableClipPlane(true);    //用于测试 g_ClipDistance 是否生效。测试通过已经。
    g_simpleTerrain->setEnableClipPlane(false); //正常绘制场景是不需要clipPlane的。
    g_simpleTerrain->render();//terrain.
    //water
    g_simpleWater->render();

    //imgui draw calls
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    glutSwapBuffers();
    glutPostRedisplay();
}